package de.tum.in.eist.im.server;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import de.tum.in.eist.im.client.ChatService;
import de.tum.in.eist.im.shared.*;

public class ChatServiceImpl extends RemoteServiceServlet implements
		ChatService {
	private static final int HEARTBEAT_INTERVAL = 30000;
	private static final int USER_EVENT_QUEUE_SIZE = 1000;
	private static final long serialVersionUID = 1L;
	private Map userEvents;
	private Calendar calendar;
	private long lastHeartbeat;
	private List<User> loggedInUsers;
	private List<User> registeredUsers;
	private List<Message> allMessages;
	private List<Group> userGroups;

	public ChatServiceImpl() {
		userEvents = new HashMap();
		initRegisteredUsers();
		initUserGroups();
		allMessages = new ArrayList<Message>();
		calendar = Calendar.getInstance();
		heartbeat();
	}


	private Group getGroupByName(String name) {
		for (Group g : userGroups) {
			if (g.getName().equals(name)) {
				return g;
			}
		}
		return null;
	}

	private User getUserByName(String name) {
		for (User u : userGroups.get(0)) {
			if (u.getUsername().equals(name)) {
				return u;
			}
		}
		return null;
	}
	
	private void initRegisteredUsers() {
		registeredUsers = new ArrayList<User>();
		
		File userFile = new File("User.config");
		FileReader fr;
		BufferedReader br;
		
		try {
			fr = new FileReader(userFile);
			br = new BufferedReader(fr);
			String usernameFromFile = br.readLine();
			String passwordFromFile = br.readLine();
			while(usernameFromFile != null) {
				User user = new User(usernameFromFile, passwordFromFile);
				registeredUsers.add(user);
				usernameFromFile = br.readLine();
				passwordFromFile = br.readLine();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void initUserGroups() {
		userGroups = new ArrayList<Group>();
		User admin = new User("Admin", "pw");
		Group loggedInUserGroup = new Group("LoggedIn", admin);
		userGroups.add(loggedInUserGroup);
	}

	public boolean registerNewUser(User newUser) {
		if (!isRegistered(newUser)) {
			registeredUsers.add(newUser);
			
			File userFile = new File("User.config");
			FileWriter fileWriterExample;
			BufferedWriter bufferedWriter;
			
			try {
				fileWriterExample = new FileWriter(userFile, true);
				bufferedWriter = new BufferedWriter(fileWriterExample);
				bufferedWriter.write(newUser.getUsername());
				bufferedWriter.newLine();
				bufferedWriter.write(newUser.getPassword());
				bufferedWriter.newLine();
				bufferedWriter.flush();
				bufferedWriter.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return true;
		} else {
			return false;
		}
	}

	private synchronized boolean isRegistered(User user) {
		
		return registeredUsers.contains(user);
	}

	public User login(User user) {
		if (isRegistered(user) && authenticate(user)) {
			if (userEvents.get(user) == null) {
				userEvents.put(user, new LinkedBlockingDeque(1000));
			}
			
			userGroups.get(0).addMember(user);
			
			GroupEvent groupEvent = new GroupEvent();
			groupEvent.setTimestamp(calendar.getTimeInMillis());
			groupEvent.setGroup(userGroups.get(0));
			groupEvent.setLogin(true);
			groupEvent.setLogUser(user);
			
			for(User onlineUser: userGroups.get(0)) {
				addEvent(onlineUser, groupEvent);
			}

			for (User rU : registeredUsers) {
				if (rU.equals(user)) {
					return rU;
				}
			}
			return null;
		} else {
			return null;
		}
	}

	private boolean authenticate(User userToAuthenticate) {
		for (User u : registeredUsers) {
			if (u.getUsername().equals(userToAuthenticate.getUsername())
					&& u.getPassword().equals(userToAuthenticate.getPassword())) {
				return true;
			}
		}
		return false;
	}

	public void sendMessage(Message message, String groupName) {
		Group g = getGroupByName(groupName);
		
		message.setToGroup(g);
		
		g.addGroupMessage(message);
		allMessages.add(message);
		
		MessageEvent messageEvent = new MessageEvent();
		messageEvent.setTimestamp(calendar.getTimeInMillis());
		messageEvent.setMessage(message);
		messageEvent.setForGroup(groupName);

		for(User groupMember: g.getGroupMembers()) {
			addEvent(groupMember, messageEvent);
		}
	}

	private void checkSendHeartbeat() {
		long now = calendar.getTimeInMillis();
		if (now - lastHeartbeat > 30000L)
			heartbeat();
	}

	protected void heartbeat() {
		lastHeartbeat = calendar.getTimeInMillis();
		HeartbeatEvent heartbeatEvent = new HeartbeatEvent();
		heartbeatEvent.setTimestamp(calendar.getTimeInMillis());
		for(User u: userGroups.get(0)) {
			addEvent(u, heartbeatEvent);
		}
	}

	private void addEvent(User loggedInUser, AbstractEvent event) {
		BlockingDeque userEventQueue = (BlockingDeque) userEvents
				.get(loggedInUser);
		if (userEventQueue.size() < 900)
			userEventQueue.add(event);
	}

	public List<AbstractEvent> getEventsBlocking(User user) {
		checkSendHeartbeat();
		BlockingDeque eventsForUser = (BlockingDeque) userEvents.get(user);
		LinkedList<AbstractEvent> result = new LinkedList<AbstractEvent>();
		do
			try {
				eventsForUser = (BlockingDeque) userEvents.get(user);
				result.add((AbstractEvent) eventsForUser.takeFirst());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		while (!eventsForUser.isEmpty());
		return result;
	}

	public List<User> getLoggedInUsers() {
		return userGroups.get(0).getGroupMembers();
	}

	public NewMessagesChunk getNewMessages(long clientLastTimestamp) {
		List<Message> newMessages = new ArrayList<Message>();
		for (int i = allMessages.size() - 1; i >= 0; i--) {
			Message message = allMessages.get(i);
			if (message.getTimestamp() > clientLastTimestamp)
				newMessages.add(0, message);
		}

		long newClientTimestamp = (new Date()).getTime();
		NewMessagesChunk result = new NewMessagesChunk();
		result.setMessages(newMessages);
		result.setTimestamp(newClientTimestamp);
		return result;
	}

	public boolean createNewGroup(Group newGroup) {
		if (!userGroups.contains(newGroup)) {
			userGroups.add(newGroup);
			GroupEvent groupEvent = new GroupEvent();
			groupEvent.setGroup(newGroup);
			groupEvent.setFounding(true);
			
			addEvent(newGroup.getFounder(), groupEvent);
			return true;
		} else {
			return false;
		}
	}

	public void invitePersonToGroup(String invitingGroupName, User askingUser,
			String invitedUserName) {
		Group invitingGroup = getGroupByName(invitingGroupName);
		User invitedUser = getUserByName(invitedUserName);

		GroupEvent groupEvent = new GroupEvent();
		groupEvent.setGroup(invitingGroup);
		groupEvent.setAskingUser(askingUser);
		groupEvent.setInvitedUser(invitedUser);
		groupEvent.setInvite(true);
		addEvent(invitedUser, groupEvent);
	}

	public void acceptGroupInvite(String groupName, User acceptingUser) {
		acceptGroupInvite(getGroupByName(groupName), acceptingUser);
	}

	public void acceptGroupInvite(Group group, User acceptingUser) {
		group.addMember(acceptingUser);
		acceptingUser.addGroupStringList(group.getName());

		GroupEvent groupEvent = new GroupEvent();
		groupEvent.setGroup(group);
		groupEvent.setInvite(false);
		addEvent(acceptingUser, groupEvent);
	}

	public List<User> getGroupMembers(String groupName) {
		return getGroupByName(groupName).getGroupMembers();
	}
	
	public List<String> getGroups(User user) {
		return user.getUserGroupList();
	}
	
	public List<Message> getHistory(Group g) {
		return g.getGroupMessages();
	}

	public boolean logout(User user) {
		if (userEvents.get(user) != null) {
			userEvents.put(user, null);
			userEvents.remove(user);
		} else {
			return false;
		}
		
		userGroups.get(0).removeMember(user);
		
		GroupEvent groupEvent = new GroupEvent();
		groupEvent.setTimestamp(calendar.getTimeInMillis());
		groupEvent.setLogin(false);
		groupEvent.setLogUser(user);
		
		for(User u: userGroups.get(0)) {
			addEvent(u, groupEvent);
		}
		return true;
	}
}
